package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"os"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/pkg/sftp"
	"golang.org/x/crypto/ssh"
)

// ===================== 1. 配置结构体定义（新增卸载相关字段） =====================
// 设备专属参数（动态键值对，新增日志路径、服务单元路径）
type DeviceParams map[string]interface{}

// 上传文件配置（部署专用）
type UploadFile struct {
	LocalPath   string `json:"localPath"`
	RemotePath  string `json:"remotePath"`
	Permissions string `json:"permissions"` // 如"0644"
}

// 解压配置（部署专用）
type UnzipConfig struct {
	RemoteTarPath string `json:"remoteTarPath"`
	RemoteDestDir string `json:"remoteDestDir"`
	CleanTar      bool   `json:"cleanTar"` // 解压后删除压缩包
}

// 单条命令配置（通用：部署/卸载命令均用此结构）
type StageCommand struct {
	Desc        string   `json:"desc"`
	Cmd         string   `json:"cmd"`
	Args        []string `json:"args"`
	IgnoreError bool     `json:"ignoreError"` // 新增：是否忽略执行错误（卸载时常用）
}

// 分阶段配置（新增Uninstall卸载阶段）
type ActionStages struct {
	PreDeploy     []StageCommand `json:"preDeploy"`     // 部署前置：停旧服务、清理旧目录
	Upload        []UploadFile   `json:"upload"`        // 部署专属：上传压缩包
	Unzip         *UnzipConfig   `json:"unzip"`         // 部署专属：解压文件
	PostDeploy    []StageCommand `json:"postDeploy"`    // 部署后置：装服务、启服务、设自启
	PreUninstall  []StageCommand `json:"preUninstall"`  // 卸载前置：停服务、关自启
	PostUninstall []StageCommand `json:"postUninstall"` // 卸载后置：删目录、清日志、删服务单元
}

// 整体部署/卸载配置（新增action字段：deploy/uninstall）
type DeployConfig struct {
	Action        string       `json:"action"`        // 操作类型：deploy（部署）/uninstall（卸载）
	Concurrency   int          `json:"concurrency"`   // 并发数
	DefaultParams DeviceParams `json:"defaultParams"` // 全局默认参数
	Stages        ActionStages `json:"stages"`        // 分阶段命令（含部署+卸载）
}

// 设备配置（不变）
type Device struct {
	Name     string       `json:"name"`
	IP       string       `json:"ip"`
	Port     int          `json:"port"`
	User     string       `json:"user"`
	Password string       `json:"password"`
	Params   DeviceParams `json:"params"`
}

// 全局配置（引用新的DeployConfig）
type GlobalConfig struct {
	Devices []Device     `json:"devices"`
	Deploy  DeployConfig `json:"deploy"`
}

// 执行结果（不变）
type ExecResult struct {
	DeviceName string
	Success    bool
	Msg        string
}

// 全局进度计数器（原子操作确保并发安全）
var (
	totalDevices int32
	completed    int32
	failed       int32
	printMutex   sync.Mutex // 避免进度打印混乱
)

// ===================== 2. 工具函数（优化占位符替换+新增路径校验） =====================
// 加载配置文件（新增action合法性校验）
func loadConfig(filePath string) (GlobalConfig, error) {
	var config GlobalConfig
	file, err := os.Open(filePath)
	if err != nil {
		return config, fmt.Errorf("打开配置文件失败：%v", err)
	}
	defer file.Close()

	decoder := json.NewDecoder(file)
	if err := decoder.Decode(&config); err != nil {
		return config, fmt.Errorf("解析配置文件失败：%v", err)
	}

	// 校验操作类型（仅支持deploy/uninstall）
	if config.Deploy.Action != "deploy" && config.Deploy.Action != "uninstall" {
		return config, fmt.Errorf("action配置错误：仅支持deploy（部署）或uninstall（卸载）")
	}

	// 默认并发数：未配置则设为10
	if config.Deploy.Concurrency <= 0 {
		config.Deploy.Concurrency = 10
	}
	return config, nil
}

// 替换命令/参数中的占位符（支持{{key}}和{{key:default}}，逻辑不变）
func replacePlaceholders(input string, deviceParams DeviceParams, defaultParams DeviceParams) string {
	re := regexp.MustCompile(`{{([^}:]+)(?::([^}]+))?}}`)

	return re.ReplaceAllStringFunc(input, func(match string) string {
		parts := re.FindStringSubmatch(match)
		if len(parts) < 2 {
			return match // 格式异常，保留原占位符
		}
		key := parts[1]
		defaultVal := ""
		if len(parts) == 3 {
			defaultVal = parts[2]
		}

		// 1. 优先取设备参数
		if val, ok := deviceParams[key]; ok {
			return fmt.Sprintf("%v", val)
		}

		// 2. 次优先取全局默认参数
		if val, ok := defaultParams[key]; ok {
			return fmt.Sprintf("%v", val)
		}

		// 3. 最后取默认值（若无则保留占位符并报警）
		if defaultVal != "" {
			return defaultVal
		}
		printMutex.Lock()
		fmt.Printf("⚠️  设备参数缺失：%s，保留原占位符\n", key)
		printMutex.Unlock()
		return match
	})
}

// 渲染命令模板（处理整条命令和参数，逻辑不变）
func renderCommand(cmd StageCommand, device Device, defaultParams DeviceParams) (string, []string) {
	renderedCmd := replacePlaceholders(cmd.Cmd, device.Params, defaultParams)
	renderedArgs := make([]string, len(cmd.Args))
	for i, arg := range cmd.Args {
		renderedArgs[i] = replacePlaceholders(arg, device.Params, defaultParams)
	}
	return renderedCmd, renderedArgs
}

// 打印设备阶段状态（新增操作类型标识：部署/卸载）
func printDeviceStage(deviceName, action, stageDesc, status string) {
	printMutex.Lock()
	defer printMutex.Unlock()

	prefix := ""
	switch status {
	case "开始":
		prefix = "🔄"
	case "成功":
		prefix = "✅"
	case "失败":
		prefix = "❌"
	case "忽略": // 新增：忽略错误时的状态
		prefix = "⚠️"
	}
	// 区分部署/卸载操作，日志更清晰
	fmt.Printf("[%s][%s] %s - %s：%s\n", deviceName, action, prefix, stageDesc, status)
}

// 打印全局进度（不变）
func printGlobalProgress(action string) {
	printMutex.Lock()
	defer printMutex.Unlock()

	done := atomic.LoadInt32(&completed)
	fail := atomic.LoadInt32(&failed)
	processed := done + fail
	progress := float64(processed) / float64(totalDevices) * 100

	fmt.Printf("\r[全局进度][%s] 已处理：%d/%d | 成功：%d | 失败：%d | 进度：%.1f%% \n",
		action, processed, totalDevices, done, fail, progress)
}

// 校验远程路径是否存在（新增：卸载时避免删除不存在的目录）
func remotePathExists(client *ssh.Client, path string) (bool, error) {
	session, err := client.NewSession()
	if err != nil {
		return false, fmt.Errorf("创建会话失败：%v", err)
	}
	defer session.Close()

	// 执行ls命令，检查路径是否存在（仅需判断退出状态）
	cmd := fmt.Sprintf("ls -d %s > /dev/null 2>&1", path)
	if err := session.Run(cmd); err != nil {
		// 退出码2表示路径不存在，属于正常情况
		if exitErr, ok := err.(*ssh.ExitError); ok && exitErr.ExitStatus() == 2 {
			return false, nil
		}
		return false, fmt.Errorf("检查路径失败：%v", err)
	}
	return true, nil
}

// ===================== 3. SSH相关函数（优化命令执行+新增路径校验） =====================
// 创建SSH客户端（不变）
func createSSHClient(device Device) (*ssh.Client, error) {
	config := &ssh.ClientConfig{
		User: device.User,
		Auth: []ssh.AuthMethod{
			ssh.Password(device.Password),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(), // 生产环境建议替换为ssh.FixedHostKey
		Timeout:         10 * time.Second,
	}

	addr := fmt.Sprintf("%s:%d", device.IP, device.Port)
	client, err := ssh.Dial("tcp", addr, config)
	if err != nil {
		return nil, fmt.Errorf("SSH连接失败：%v", err)
	}
	return client, nil
}

// 执行远程命令（新增IgnoreError逻辑，支持忽略非致命错误）
func execRemoteCmd(client *ssh.Client, cmd StageCommand, device Device, defaultParams DeviceParams) error {
	session, err := client.NewSession()
	if err != nil {
		return fmt.Errorf("创建SSH会话失败：%v", err)
	}
	defer session.Close()

	// 捕获命令输出（用于错误调试）
	var outputBuf bytes.Buffer
	session.Stdout = &outputBuf
	session.Stderr = &outputBuf

	// 渲染命令（替换占位符）
	renderedCmd, renderedArgs := renderCommand(cmd, device, defaultParams)
	// 拼接命令字符串（含参数）
	fullCmd := fmt.Sprintf("%s %s", renderedCmd, strings.Join(renderedArgs, " "))
	// 通过sh -c执行，支持管道、重定向等shell语法
	execCmd := fmt.Sprintf("sh -c '%s'", fullCmd)
	//fmt.Println(execCmd)
	// 执行命令
	if err := session.Run(execCmd); err != nil {
		// 若配置IgnoreError，忽略错误并返回nil
		if cmd.IgnoreError {
			printDeviceStage(device.Name, "uninstall", cmd.Desc, "忽略")
			fmt.Printf("⚠️  %s：命令执行失败（已忽略），输出：%s\n", device.Name, outputBuf.String())
			return nil
		}
		// 不忽略错误，返回详细信息
		return fmt.Errorf("命令执行失败（输出：%s）：%v", outputBuf.String(), err)
	}
	return nil
}

// 上传文件到远程设备（部署专用，逻辑不变）
func uploadRemoteFile(client *ssh.Client, upload UploadFile, device Device, defaultParams DeviceParams) error {
	// 创建SFTP客户端
	sftpClient, err := sftp.NewClient(client)
	if err != nil {
		return fmt.Errorf("创建SFTP客户端失败：%v", err)
	}
	defer sftpClient.Close()

	// 渲染路径（支持占位符，如动态文件名）
	localPath := replacePlaceholders(upload.LocalPath, device.Params, defaultParams)
	remotePath := replacePlaceholders(upload.RemotePath, device.Params, defaultParams)

	// 验证本地文件存在
	if _, err := os.Stat(localPath); os.IsNotExist(err) {
		return fmt.Errorf("本地文件不存在：%s", localPath)
	}

	// 打开本地文件
	localFile, err := os.Open(localPath)
	if err != nil {
		return fmt.Errorf("打开本地文件失败：%v", err)
	}
	defer localFile.Close()

	// 创建远程文件
	remoteFile, err := sftpClient.Create(remotePath)
	if err != nil {
		return fmt.Errorf("创建远程文件失败：%v", err)
	}
	defer remoteFile.Close()

	// 设置远程文件权限
	permStr := replacePlaceholders(upload.Permissions, device.Params, defaultParams)
	perm, err := strconv.ParseInt(permStr, 8, 32)
	if err != nil {
		return fmt.Errorf("解析文件权限失败（%s）：%v", permStr, err)
	}
	if err := remoteFile.Chmod(os.FileMode(perm)); err != nil {
		return fmt.Errorf("设置远程文件权限失败：%v", err)
	}

	// 传输文件（显示进度可扩展，此处简化）
	if _, err := io.Copy(remoteFile, localFile); err != nil {
		return fmt.Errorf("文件传输失败：%v", err)
	}
	return nil
}

// ===================== 4. 核心流程（新增卸载流程+重构部署流程） =====================
// 单设备部署流程（原有逻辑重构，适配新配置结构）
func deploySingleDevice(device Device, config DeployConfig) ExecResult {
	action := "deploy"
	// 1. 创建SSH连接
	client, err := createSSHClient(device)
	if err != nil {
		return ExecResult{
			DeviceName: device.Name,
			Success:    false,
			Msg:        fmt.Sprintf("SSH连接失败：%v", err),
		}
	}
	defer client.Close()

	// 2. 执行部署前置阶段（停旧服务、清理旧目录）
	for _, cmd := range config.Stages.PreDeploy {
		printDeviceStage(device.Name, action, cmd.Desc, "开始")
		if err := execRemoteCmd(client, cmd, device, config.DefaultParams); err != nil {
			printDeviceStage(device.Name, action, cmd.Desc, "失败")
			return ExecResult{
				DeviceName: device.Name,
				Success:    false,
				Msg:        fmt.Sprintf("前置阶段失败（%s）：%v", cmd.Desc, err),
			}
		}
		printDeviceStage(device.Name, action, cmd.Desc, "成功")
	}

	// 3. 执行上传阶段（部署专属）
	for i, upload := range config.Stages.Upload {
		stageDesc := fmt.Sprintf("上传文件（%d/%d）", i+1, len(config.Stages.Upload))
		printDeviceStage(device.Name, action, stageDesc, "开始")
		if err := uploadRemoteFile(client, upload, device, config.DefaultParams); err != nil {
			printDeviceStage(device.Name, action, stageDesc, "失败")
			return ExecResult{
				DeviceName: device.Name,
				Success:    false,
				Msg:        fmt.Sprintf("文件上传失败：%v", err),
			}
		}
		printDeviceStage(device.Name, action, stageDesc, "成功")
	}

	// 4. 执行解压阶段（部署专属）
	if config.Stages.Unzip != nil {
		stageDesc := "解压文件"
		printDeviceStage(device.Name, action, stageDesc, "开始")

		// 渲染解压参数
		remoteTar := replacePlaceholders(config.Stages.Unzip.RemoteTarPath, device.Params, config.DefaultParams)
		remoteDest := replacePlaceholders(config.Stages.Unzip.RemoteDestDir, device.Params, config.DefaultParams)
		// 校验远程目录存在（不存在则创建）
		if exists, err := remotePathExists(client, remoteDest); err != nil {
			printDeviceStage(device.Name, action, stageDesc, "失败")
			return ExecResult{
				DeviceName: device.Name,
				Success:    false,
				Msg:        fmt.Sprintf("检查解压目录失败：%v", err),
			}
		} else if !exists {
			mkdirCmd := StageCommand{
				Desc: "创建解压目录",
				Cmd:  "mkdir",
				Args: []string{"-p", remoteDest},
			}
			if err := execRemoteCmd(client, mkdirCmd, device, config.DefaultParams); err != nil {
				printDeviceStage(device.Name, action, "创建解压目录", "失败")
				return ExecResult{
					DeviceName: device.Name,
					Success:    false,
					Msg:        fmt.Sprintf("创建解压目录失败：%v", err),
				}
			}
			printDeviceStage(device.Name, action, "创建解压目录", "成功")
		}

		// 执行解压命令（带--strip-components=1，去除顶层目录）
		unzipCmd := StageCommand{
			Desc: "执行文件解压",
			Cmd:  "tar",
			Args: []string{"-zxf", remoteTar, "-C", remoteDest},
		}
		if err := execRemoteCmd(client, unzipCmd, device, config.DefaultParams); err != nil {
			printDeviceStage(device.Name, action, stageDesc, "失败")
			return ExecResult{
				DeviceName: device.Name,
				Success:    false,
				Msg:        fmt.Sprintf("解压失败：%v", err),
			}
		}

		// 解压后删除压缩包（若配置开启）
		if config.Stages.Unzip.CleanTar {
			rmTarCmd := StageCommand{
				Desc: "删除压缩包",
				Cmd:  "rm",
				Args: []string{"-f", remoteTar},
			}
			if err := execRemoteCmd(client, rmTarCmd, device, config.DefaultParams); err != nil {
				printDeviceStage(device.Name, action, "删除压缩包", "警告")
				fmt.Printf("⚠️  %s：压缩包删除失败（不影响服务）：%v\n", device.Name, err)
			} else {
				printDeviceStage(device.Name, action, "删除压缩包", "成功")
			}
		}
		printDeviceStage(device.Name, action, stageDesc, "成功")
	}

	// 5. 执行部署后置阶段（安装服务、启动服务、设自启）
	for _, cmd := range config.Stages.PostDeploy {
		printDeviceStage(device.Name, action, cmd.Desc, "开始")
		if err := execRemoteCmd(client, cmd, device, config.DefaultParams); err != nil {
			printDeviceStage(device.Name, action, cmd.Desc, "失败")
			return ExecResult{
				DeviceName: device.Name,
				Success:    false,
				Msg:        fmt.Sprintf("后置阶段失败（%s）：%v", cmd.Desc, err),
			}
		}
		printDeviceStage(device.Name, action, cmd.Desc, "成功")
	}

	// 6. 部署完成（更新进度）
	atomic.AddInt32(&completed, 1)
	printGlobalProgress(action)
	return ExecResult{
		DeviceName: device.Name,
		Success:    true,
		Msg:        "部署完成",
	}
}

// ===================== 5. 单设备卸载流程（新增核心逻辑） =====================
func uninstallSingleDevice(device Device, config DeployConfig) ExecResult {
	action := "uninstall"
	// 1. 创建SSH连接
	client, err := createSSHClient(device)
	if err != nil {
		return ExecResult{
			DeviceName: device.Name,
			Success:    false,
			Msg:        fmt.Sprintf("SSH连接失败：%v", err),
		}
	}
	defer client.Close()

	// 2. 执行卸载前置阶段（停止服务、关闭自启）
	for _, cmd := range config.Stages.PreUninstall {
		printDeviceStage(device.Name, action, cmd.Desc, "开始")
		if err := execRemoteCmd(client, cmd, device, config.DefaultParams); err != nil {
			// 卸载前置阶段失败不强制终止（如服务已停止），但记录警告
			printDeviceStage(device.Name, action, cmd.Desc, "警告")
			fmt.Printf("⚠️  %s：卸载前置阶段警告（%s）：%v\n", device.Name, cmd.Desc, err)
		} else {
			printDeviceStage(device.Name, action, cmd.Desc, "成功")
		}
	}

	// 3. 执行卸载后置阶段（删除部署目录、清理日志、删除服务单元）
	for _, cmd := range config.Stages.PostUninstall {
		printDeviceStage(device.Name, action, cmd.Desc, "开始")
		// 先校验目标路径是否存在（避免删除不存在的资源）
		targetPath := ""
		// 提取命令中的路径参数（适配rm/rmdir命令）
		if cmd.Cmd == "rm" && len(cmd.Args) >= 2 {
			targetPath = replacePlaceholders(cmd.Args[1], device.Params, config.DefaultParams)
		}
		if targetPath != "" {
			exists, err := remotePathExists(client, targetPath)
			if err != nil {
				printDeviceStage(device.Name, action, cmd.Desc, "失败")
				return ExecResult{
					DeviceName: device.Name,
					Success:    false,
					Msg:        fmt.Sprintf("检查目标路径失败（%s）：%v", targetPath, err),
				}
			}
			if !exists {
				printDeviceStage(device.Name, action, cmd.Desc, "忽略")
				fmt.Printf("ℹ️  %s：目标路径不存在（%s），跳过删除\n", device.Name, targetPath)
				continue
			}
		}

		// 执行卸载命令
		if err := execRemoteCmd(client, cmd, device, config.DefaultParams); err != nil {
			printDeviceStage(device.Name, action, cmd.Desc, "失败")
			return ExecResult{
				DeviceName: device.Name,
				Success:    false,
				Msg:        fmt.Sprintf("卸载后置阶段失败（%s）：%v", cmd.Desc, err),
			}
		}
		printDeviceStage(device.Name, action, cmd.Desc, "成功")
	}

	// 4. 卸载完成（更新进度）
	atomic.AddInt32(&completed, 1)
	printGlobalProgress(action)
	return ExecResult{
		DeviceName: device.Name,
		Success:    true,
		Msg:        "卸载完成",
	}
}

// ===================== 6. 批量部署/卸载控制（适配双流程） =====================
func batchAction(config GlobalConfig) {
	action := config.Deploy.Action
	// 初始化进度计数器
	totalDevices = int32(len(config.Devices))
	completed = 0
	failed = 0

	// 结果通道（收集各设备执行结果）
	resultChan := make(chan ExecResult, totalDevices)
	// 并发控制通道（控制同时执行的设备数）
	semaphore := make(chan struct{}, config.Deploy.Concurrency)

	// 启动所有设备执行协程（根据action选择部署/卸载）
	for _, device := range config.Devices {
		semaphore <- struct{}{}
		go func(d Device) {
			defer func() {
				// 释放并发名额
				<-semaphore
				// 捕获协程panic（避免单个设备异常导致整体崩溃）
				if r := recover(); r != nil {
					errMsg := fmt.Sprintf("协程异常：%v", r)
					resultChan <- ExecResult{
						DeviceName: d.Name,
						Success:    false,
						Msg:        errMsg,
					}
					atomic.AddInt32(&failed, 1)
					printGlobalProgress(action)
				}
			}()

			// 根据action执行对应流程
			var result ExecResult
			if action == "deploy" {
				result = deploySingleDevice(d, config.Deploy)
			} else {
				result = uninstallSingleDevice(d, config.Deploy)
			}
			if !result.Success {
				atomic.AddInt32(&failed, 1)
				printGlobalProgress(action)
			}
			resultChan <- result
		}(device)
	}

	// 等待所有设备执行完成并收集结果
	var results []ExecResult
	for i := 0; i < int(totalDevices); i++ {
		results = append(results, <-resultChan)
	}
	close(resultChan)
	close(semaphore)

	// ===================== 7. 输出最终执行报告 =====================
	printMutex.Lock()
	defer printMutex.Unlock()
	fmt.Println("\n\n" + strings.Repeat("=", 60))
	if action == "deploy" {
		fmt.Println("          批量部署最终报告")
	} else {
		fmt.Println("          批量卸载最终报告")
	}
	fmt.Println(strings.Repeat("=", 60))
	fmt.Printf("总设备数：%d | 成功：%d | 失败：%d\n", totalDevices, completed, failed)
	fmt.Println(strings.Repeat("-", 60))

	// 输出失败设备详情
	if failed > 0 {
		fmt.Printf("❌ %s失败设备详情：\n", strings.Title(action))
		for _, res := range results {
			if !res.Success {
				fmt.Printf("  %s：%s\n", res.DeviceName, res.Msg)
			}
		}
	} else {
		if action == "deploy" {
			fmt.Println("✅ 所有设备部署成功！")
		} else {
			fmt.Println("✅ 所有设备卸载成功！")
		}
	}
	fmt.Println(strings.Repeat("=", 60))
}

// ===================== 8. 主函数（程序入口，适配双流程） =====================
func main() {
	// 1. 使用flag包定义配置文件路径参数
	var configPath string
	flag.StringVar(&configPath, "f", "", "配置文件路径（必填），例如：-f deploy.json")
	flag.Usage = func() {
		fmt.Println("用法：servicectl -f <配置文件路径>")
		fmt.Println("示例：")
		fmt.Println("  部署操作：./servicectl -f deploy.json")
		fmt.Println("  卸载操作：./servicectl -f uninstall.json")
		fmt.Println("参数说明：")
		flag.PrintDefaults()
	}
	flag.Parse()

	// 检查配置文件路径是否提供
	if configPath == "" {
		fmt.Println("错误：请通过 -f 指定配置文件路径")
		flag.Usage()
		os.Exit(1)
	}

	// 2. 加载配置
	fmt.Println("🔍 正在加载配置文件...")
	config, err := loadConfig(configPath)
	if err != nil {
		fmt.Printf("❌ 配置加载失败：%v\n", err)
		os.Exit(1)
	}
	if len(config.Devices) == 0 {
		fmt.Println("❌ 配置文件中未发现设备信息")
		os.Exit(1)
	}

	// 3. 显示执行基本信息
	action := config.Deploy.Action
	fmt.Printf("📋 %s基本信息：\n", strings.Title(action))
	fmt.Printf("  - 目标设备数：%d\n", len(config.Devices))
	fmt.Printf("  - 并发%s数：%d\n", action, config.Deploy.Concurrency)
	if action == "deploy" {
		fmt.Printf("  - 执行阶段：前置处理 → 文件上传 → 解压 → 后置启动\n")
	} else {
		fmt.Printf("  - 执行阶段：前置停服 → 后置清理（目录/日志/服务单元）\n")
	}
	fmt.Println(strings.Repeat("=", 60))
	fmt.Printf("🚀 开始批量%s（按Ctrl+C可终止）...\n", action)
	fmt.Println()

	// 4. 启动批量执行（部署/卸载统一入口）
	batchAction(config)
}
